﻿//
//  Monkey Manager (Based on nVLC (by Roman Ginzburg))
//  Copyright (C) 2010 Victor Tereschenko (aka sibvic)
//
//  This program is free software: you can redistribute it and/or modify
//  it under the terms of the GNU General Public License as published by
//  the Free Software Foundation, either version 3 of the License, or
//  (at your option) any later version.
//
//  This program is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU General Public License for more details.
//
//  You should have received a copy of the GNU General Public License
//  along with this program.  If not, see <http://www.gnu.org/licenses/>.
//     
// ========================================================================

using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Forms;
using ManagerDB;
using Declarations;
using Declarations.Events;
using Declarations.Media;
using Declarations.Players;
using Implementation;

namespace MonkeyManager
{
    /// <summary>
    /// Interaction logic for PlayerWindow.xaml
    /// </summary>
    public partial class PlayerWindow : Window
    {
        IMediaPlayerFactory mPlayerFactory;
        IVideoPlayer mPlayer;
        IMedia mMedia;
        IFileSource mFileSource;
        StoredFile mPlayingFile;
        IDatabase mDatabase;
        PlayersController mPlayerController;
        PlayerActionSimpleDelegate mOnPlay;
        PlayerActionSimpleDelegate mOnStop;
        PlayerActionSimpleDelegate mOnActivate;
        PlayerActionSimpleDelegate mOnMaximized;
        PlayerActionSimpleDelegate mOnDemaximized;
        PlayerActionSimpleDelegate mOnClosed;

        public PlayerWindow(IFileSource source, IDatabase database, PlayersController playerController)
        {
            mFileSource = source;
            mDatabase = database;
            mPlayerController = playerController;
            mOnPlay = new PlayerActionSimpleDelegate(onPlay);
            mOnStop = new PlayerActionSimpleDelegate(onStop);
            mOnMaximized = new PlayerActionSimpleDelegate(mPlayerController_OnMaximized);
            mOnDemaximized = new PlayerActionSimpleDelegate(mPlayerController_OnDemaximized);
            mOnClosed = new PlayerActionSimpleDelegate(mPlayerController_OnClosed);
            mPlayerController.OnPlay += mOnPlay;
            mPlayerController.OnStop += mOnStop;
            mPlayerController.OnMaximized += mOnMaximized;
            mPlayerController.OnDemaximized += mOnDemaximized;
            mPlayerController.OnClosed += mOnClosed;
            mOnActivate = new PlayerActionSimpleDelegate(mPlayerController_OnActivated);
            mPlayerController.OnActivated += mOnActivate;
            InitializeComponent();

            mPlayerCanvas = new System.Windows.Forms.Panel();
            mPlayerCanvas.BackColor = System.Drawing.Color.Black;
            windowsFormsHost1.Child = mPlayerCanvas;
            label3.ContextMenu = createContextMenu();
            label1.ContextMenu = label3.ContextMenu;

            mPlayerFactory = new MediaPlayerFactory();
            mPlayer = mPlayerFactory.CreatePlayer<IVideoPlayer>();

            mPlayer.Events.TimeChanged += new EventHandler<MediaPlayerTimeChanged>(Events_TimeChanged);
            mPlayer.Events.MediaEnded += new EventHandler(Events_MediaEnded);
            mPlayer.Events.PlayerStopped += new EventHandler(Events_PlayerStopped);

            mPlayer.WindowHandle = mPlayerCanvas.Handle;
            mPlayer.KeyInputEnabled = false;
            windowsFormsHost1.KeyDown += new System.Windows.Input.KeyEventHandler(windowsFormsHost1_KeyDown);
            slider2.Value = mPlayer.Volume;

            syncCheckBox.IsChecked = true;

            positionComboBox.Items.Add("TL");
            positionComboBox.Items.Add("TR");
            positionComboBox.Items.Add("BL");
            positionComboBox.Items.Add("BR");
            Open(mFileSource.Next());

            mLastVolume = mPlayer.Volume;
        }

        void windowsFormsHost1_KeyDown(object sender, System.Windows.Input.KeyEventArgs e)
        {
            switch (e.Key)
            {
                case System.Windows.Input.Key.Enter:
                    if (WindowStyle == System.Windows.WindowStyle.None)
                        EscapeFullscreen();
                    else
                        EnterFullscreen();
                    break;
                case System.Windows.Input.Key.B:
                    // Boss key
                    pause();
                    if (WindowStyle == System.Windows.WindowStyle.None)
                        EscapeFullscreen();
                    WindowState = System.Windows.WindowState.Minimized;
                    e.Handled = true;
                    break;
                case System.Windows.Input.Key.N:
                    updateVideoPosition();
                    mDatabase.Update(mPlayingFile);
                    if (!playNextVideo())
                    {
                        mStatus = Status.StoppedManually;
                        pause();
                    }
                    break;
                case System.Windows.Input.Key.M:
                    if (System.Windows.MessageBox.Show("Are you sure?", "Mark as watched?", System.Windows.MessageBoxButton.YesNo) != MessageBoxResult.Yes)
                        break;
                    markAsWatched();
                    playNextVideo();
                    break;
                case System.Windows.Input.Key.D0:
                    speedOnOff();
                    break;
                case System.Windows.Input.Key.D1:
                    mPlayer.PlaybackRate = 1.1f;
                    break;
                case System.Windows.Input.Key.D2:
                    mPlayer.PlaybackRate = 1.2f;
                    break;
                case System.Windows.Input.Key.D3:
                    mPlayer.PlaybackRate = 1.3f;
                    break;
                case System.Windows.Input.Key.D4:
                    mPlayer.PlaybackRate = 1.4f;
                    break;
                case System.Windows.Input.Key.D5:
                    mPlayer.PlaybackRate = 1.5f;
                    break;
                case System.Windows.Input.Key.D6:
                    mPlayer.PlaybackRate = 1.6f;
                    break;
                case System.Windows.Input.Key.D7:
                    mPlayer.PlaybackRate = 1.7f;
                    break;
                case System.Windows.Input.Key.D8:
                    mPlayer.PlaybackRate = 1.8f;
                    break;
                case System.Windows.Input.Key.D9:
                    mPlayer.PlaybackRate = 1.9f;
                    break;
                case System.Windows.Input.Key.Escape:
                    if (WindowStyle == System.Windows.WindowStyle.None)
                        EscapeFullscreen();
                    break;
                case System.Windows.Input.Key.Space:
                    pausePlay();
                    break;
                case System.Windows.Input.Key.Left:
                    {
                        int seek = getSeekSeconds();
                        seekSeconds(-seek);
                    }
                    e.Handled = true;
                    break;
                case System.Windows.Input.Key.Right:
                    {
                        int seek = getSeekSeconds();
                        seekSeconds(seek);
                    }
                    e.Handled = true;
                    break;
                case System.Windows.Input.Key.Up:
                    {
                        int gain = getVolumeLeap();
                        mPlayer.Volume = Math.Min(100, mPlayer.Volume + gain);
                    }
                    e.Handled = true;
                    break;
                case System.Windows.Input.Key.Down:
                    {
                        int gain = getVolumeLeap();
                        mPlayer.Volume = Math.Max(0, mPlayer.Volume - gain);
                    }
                    e.Handled = true;
                    break;
            }
        }

        private static int getVolumeLeap()
        {
            int gain = 1;
            if ((Control.ModifierKeys & Keys.Alt) != 0)
                gain *= 2;
            if ((Control.ModifierKeys & Keys.Control) != 0)
                gain *= 3;
            if ((Control.ModifierKeys & Keys.Shift) != 0)
                gain *= 4;
            return gain;
        }

        long mLastJumpTo = 0;

        /// <summary>
        /// Seeks given number of seconds.
        /// </summary>
        /// <param name="seconds">Seconds to seek.</param>
        private void seekSeconds(int seconds)
        {
            if (seconds > 0 && mPlayer.Time < mLastJumpTo)
                seconds = Math.Max(seconds, (int)((mLastJumpTo - mPlayer.Time) / 1000));
            mLastJumpTo = Math.Min(mPlayer.Length, Math.Max(0, mPlayer.Time + seconds * 1000));
            mPlayer.Time = mLastJumpTo;
        }

        private static int getSeekSeconds()
        {
            int seek = 5;
            if ((Control.ModifierKeys & Keys.Alt) != 0)
                seek *= 2;
            if ((Control.ModifierKeys & Keys.Control) != 0)
                seek *= 3;
            if ((Control.ModifierKeys & Keys.Shift) != 0)
                seek *= 4;
            return seek;
        }

        private void EnterFullscreen()
        {
            WindowStyle = System.Windows.WindowStyle.None;
            WindowState = System.Windows.WindowState.Maximized;
            mLastWidth = mPlayerCanvas.Width;
            mLastHeight = mPlayerCanvas.Height;
            mPlayerCanvas.Top = 0;
            mPlayerCanvas.Left = 0;
            var screen = Screen.FromHandle(mPlayerCanvas.Handle);
            mPlayerCanvas.Width = screen.Bounds.Width;
            mPlayerCanvas.Height = screen.Bounds.Height;
            mLastGridLength = MainGrid.RowDefinitions[1].Height;
            MainGrid.RowDefinitions[1].Height = new GridLength(0);
        }

        private void EscapeFullscreen()
        {
            WindowStyle = System.Windows.WindowStyle.ToolWindow;
            WindowState = System.Windows.WindowState.Normal;
            MainGrid.RowDefinitions[1].Height = mLastGridLength;
            mPlayerCanvas.Width = mLastWidth;
            mPlayerCanvas.Height = mLastHeight;
        }

        System.Windows.Forms.Panel mPlayerCanvas;
        GridLength mLastGridLength;
        int mLastWidth = 0;
        int mLastHeight = 0;

        void Open(StoredFile file)
        {
            mPlayingFile = file;
            mLastJumpTo = 0;

            string fullFileName = mDatabase.GetPath(mPlayingFile);
            if (fullFileName == null)
                return;
            mMedia = mPlayerFactory.CreateMedia<IMedia>(fullFileName, new string[] { });
            mMedia.Events.DurationChanged += new EventHandler<MediaDurationChange>(Events_DurationChanged);
            mMedia.Events.StateChanged += new EventHandler<MediaStateChange>(Events_StateChanged);

            mPlayer.Open(mMedia);
            mMedia.Parse(true);
            Title = mPlayingFile.Title == null ? mPlayingFile.File.FileName : mPlayingFile.Title;
        }

        int mLastVolume;
        void mPlayerController_OnActivated(PlayerWindow sender)
        {
            if (mStatus != Status.Playing)
                return;

            if (sender == this)
            {
                mPlayer.Mute = false;
                mPlayer.Volume = mLastVolume;
            }
            else
            {
                if (!mPlayer.Mute)
                    mLastVolume = mPlayer.Volume;
                mPlayer.Mute = true;
            }
        }

        private void Window_Closing_1(object sender, System.ComponentModel.CancelEventArgs e)
        {
            updateVideoPosition();

            mPlayer.Stop();
            mPlayer.Dispose();
            if (mMedia != null)
                mMedia.Dispose();

            mDatabase.Update(mPlayingFile);
            mPlayerController.SendClosed(this);
            mPlayerController.OnMaximized -= mOnMaximized;
            mPlayerController.OnDemaximized -= mOnDemaximized;
            mPlayerController.OnClosed -= mOnClosed;
            mPlayerController.OnPlay -= mOnPlay;
            mPlayerController.OnStop -= mOnStop;
            mPlayerController.OnActivated -= mOnActivate;
        }

        private void updateVideoPosition()
        {
            if (mPlayer.Time > 0 && mPlayer.Length > 0)
            {
                mPlayingFile.File.LastPosition = mPlayer.Time / 1000.0;
                mPlayingFile.File.Count = (int)mPlayingFile.File.Count + (double)mPlayer.Time / (double)mPlayer.Length;
            }
        }
        
        enum Status
        {
            StoppedManually,
            StoppedAutomatically,
            Playing
        }

        Status mStatus = Status.StoppedManually;

        void mPlayerController_OnClosed(PlayerWindow sender)
        {
            if (sender == this)
                return;

            if (isOnSameScreen(this, sender) && mStatus == Status.StoppedAutomatically)
            {
                mStatus = Status.Playing;
                mPlayer.Play();
            }
        }

        void mPlayerController_OnDemaximized(PlayerWindow sender)
        {
            if (sender == this)
                return;

            if (isOnSameScreen(this, sender) && mStatus == Status.StoppedAutomatically)
            {
                mStatus = Status.Playing;
                mPlayer.Play();
            }
        }

        void mPlayerController_OnMaximized(PlayerWindow sender)
        {
            if (sender == this)
                return;

            if (isOnSameScreen(this, sender))
            {
                mStatus = Status.StoppedAutomatically;
                mPlayer.Pause();
            }
        }

        void onPlay(PlayerWindow sender)
        {
            if (syncCheckBox.IsChecked == false || sender == this || mStatus == Status.Playing)
                return;

            if (!isOnSameScreen(this, sender) || sender.WindowState != System.Windows.WindowState.Maximized)
            {
                mStatus = Status.Playing;
                mPlayer.Play();
            }
        }

        void onStop(PlayerWindow sender)
        {
            if (syncCheckBox.IsChecked == false || sender == this || mStatus != Status.Playing)
                return;

            mStatus = Status.StoppedManually;
            mPlayer.Pause();
        }

        private bool isOnSameScreen(Window left, Window right)
        {
            Screen leftScreen = Screen.FromPoint(new System.Drawing.Point((int)left.Left, (int)left.Top));
            Screen rightScreen = Screen.FromPoint(new System.Drawing.Point((int)right.Left, (int)right.Top));
            return leftScreen.DeviceName == rightScreen.DeviceName;
        }

        private void InitControls()
        {
            label1.Content = "00:00:00";
            label3.Content = "00:00:00";
        }

        #region Button handlers
        private void pausePlay()
        {
            if (mStatus == Status.Playing)
                pause();
            else
                play();
        }

        private void playButton_Click(object sender, RoutedEventArgs e)
        {
            play();
        }

        private void play()
        {
            if (syncCheckBox.IsChecked == true)
                mPlayerController.Play(this);
            mPlayer.Play();
            mStatus = Status.Playing;
            if (syncCheckBox.IsChecked == true)
                mPlayerController.SendActivated(this);
        }

        private void pauseButton_Click(object sender, RoutedEventArgs e)
        {
            pause();
        }

        private void pause()
        {
            if (syncCheckBox.IsChecked == true)
                mPlayerController.Stop(this);
            mPlayer.Pause();
            mStatus = Status.StoppedManually;
            if (syncCheckBox.IsChecked == true)
                mPlayerController.SendActivated(null);
        }

        private void slider2_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
        {
            if (mPlayer != null)
            {
                mPlayer.Volume = (int)e.NewValue;
                mLastVolume = mPlayer.Volume;
            }
        }

        private void markAsWatched()
        {
            this.Dispatcher.BeginInvoke(new Action(delegate
            {
                InitControls();
            }));
            mPlayingFile.File.Count = (int)mPlayingFile.File.Count + 1.0;
            mDatabase.Update(mPlayingFile);
        }
        #endregion

        #region Menu handlers
        private System.Windows.Controls.MenuItem addMenuItem(string name, RoutedEventHandler handler)
        {
            System.Windows.Controls.MenuItem item = new System.Windows.Controls.MenuItem();
            item.Header = name;
            item.Click += handler;
            return item;
        }

        private System.Windows.Controls.ContextMenu createContextMenu()
        {
            System.Windows.Controls.ContextMenu menu = new System.Windows.Controls.ContextMenu();

            menu.Items.Add(addMenuItem("Default", new RoutedEventHandler(onDefaultAspect_Click)));
            menu.Items.Add(addMenuItem("4:3", new RoutedEventHandler(on4To3Aspect_Click)));
            menu.Items.Add(addMenuItem("5:4", new RoutedEventHandler(on5To4Aspect_Click)));
            menu.Items.Add(addMenuItem("16:10", new RoutedEventHandler(on16To10Aspect_Click)));
            menu.Items.Add(addMenuItem("16:9", new RoutedEventHandler(on16To9Aspect_Click)));
            menu.Items.Add(addMenuItem("2.21:1", new RoutedEventHandler(on2_21To1Aspect_Click)));
            menu.Items.Add(addMenuItem("2.35:1", new RoutedEventHandler(on2_35To1Aspect_Click)));
            menu.Items.Add(addMenuItem("2.39:1", new RoutedEventHandler(on2_39To1Aspect_Click)));
            menu.Items.Add(addMenuItem("1:1", new RoutedEventHandler(on1To1Aspect_Click)));
            menu.Items.Add(addMenuItem("No deinterlacing", new RoutedEventHandler(onNoDeint_Click)));
            menu.Items.Add(addMenuItem("Yadif", new RoutedEventHandler(onYadiff_Click)));
            menu.Items.Add(addMenuItem("Yadifx2", new RoutedEventHandler(onYadiff2_Click)));
            return menu;
        }

        #region speed control
        float mSpeed = 1.0f;
        private void speedOnOff()
        {
            if (mPlayer.PlaybackRate == 1.0f)
                mPlayer.PlaybackRate = mSpeed;
            else
            {
                mSpeed = mPlayer.PlaybackRate;
                mPlayer.PlaybackRate = 1.0f;
            }
        }
        #endregion

        private void onNoDeint_Click(object sender, RoutedEventArgs e)
        {
            mPlayer.Deinterlace.Enabled = false;
        }

        private void onYadiff_Click(object sender, RoutedEventArgs e)
        {
            mPlayer.Deinterlace.Enabled = true;
            mPlayer.Deinterlace.Mode = DeinterlaceMode.yadif;
        }

        private void onYadiff2_Click(object sender, RoutedEventArgs e)
        {
            mPlayer.Deinterlace.Enabled = true;
            mPlayer.Deinterlace.Mode = DeinterlaceMode.yadif2x;
        }

        private void onDefaultAspect_Click(object sender, RoutedEventArgs e)
        {
            mPlayer.AspectRatio = AspectRatioMode.Default;
        }

        private void on4To3Aspect_Click(object sender, RoutedEventArgs e)
        {
            mPlayer.AspectRatio = AspectRatioMode.Mode4_3;
        }

        private void on5To4Aspect_Click(object sender, RoutedEventArgs e)
        {
            mPlayer.AspectRatio = AspectRatioMode.Mode5_4;
        }

        private void on16To10Aspect_Click(object sender, RoutedEventArgs e)
        {
            mPlayer.AspectRatio = AspectRatioMode.Mode16_10;
        }

        private void on16To9Aspect_Click(object sender, RoutedEventArgs e)
        {
            mPlayer.AspectRatio = AspectRatioMode.Mode16_9;
        }

        private void on2_21To1Aspect_Click(object sender, RoutedEventArgs e)
        {
            mPlayer.AspectRatio = AspectRatioMode.Mode2_21_1;
        }

        private void on2_35To1Aspect_Click(object sender, RoutedEventArgs e)
        {
            mPlayer.AspectRatio = AspectRatioMode.Mode2_35_1;
        }

        private void on2_39To1Aspect_Click(object sender, RoutedEventArgs e)
        {
            mPlayer.AspectRatio = AspectRatioMode.Mode2_39_1;
        }

        private void on1To1Aspect_Click(object sender, RoutedEventArgs e)
        {
            mPlayer.AspectRatio = AspectRatioMode.Mode1_1;
        }
        #endregion

        #region Events
        void Events_StateChanged(object sender, MediaStateChange e)
        {
            switch (e.NewState)
            {
                case MediaState.NothingSpecial:
                case MediaState.Buffering:
                // do nothing
                    break;
                case MediaState.Opening:
                    {
                        double position = mPlayingFile.File.Count - (int)mPlayingFile.File.Count;
                        if (position > 0 && mPlayingFile.File.LastPosition == 0)
                            mPlayingFile.File.LastPosition = mPlayer.Length * position;
                        mPlayer.Time = (long)Math.Round(mPlayingFile.File.LastPosition * 1000.0);
                        mPlayer.Pause();
                    }
                    break;
                case MediaState.Playing:
                    this.Dispatcher.BeginInvoke(new Action(delegate
                    {
                        playButton.Visibility = System.Windows.Visibility.Hidden;
                        pauseButton.Visibility = System.Windows.Visibility.Visible;
                    }));
                    break;
                case MediaState.Paused:
                case MediaState.Stopped:
                case MediaState.Error:
                    this.Dispatcher.BeginInvoke(new Action(delegate
                    {
                        playButton.Visibility = System.Windows.Visibility.Visible;
                        pauseButton.Visibility = System.Windows.Visibility.Hidden;
                    }));
                    break;
                case MediaState.Ended:
                    this.Dispatcher.BeginInvoke(new Action(delegate
                    {
                        playButton.Visibility = System.Windows.Visibility.Visible;
                        pauseButton.Visibility = System.Windows.Visibility.Hidden;
                        mPlayer.Position = 0.0f;
                    }));
                    break;
            }
        }

        void Events_PlayerStopped(object sender, EventArgs e)
        {
            this.Dispatcher.BeginInvoke(new Action(delegate
            {
                InitControls();
            }));
        }

        void Events_MediaEnded(object sender, EventArgs e)
        {
            markAsWatched();
            if (!playNextVideo())
                mStatus = Status.StoppedManually;
        }

        private bool playNextVideo()
        {
            StoredFile file = mFileSource.Next();
            if (file == null)
                return false;
            else
            {
                this.Dispatcher.BeginInvoke(new Action(delegate
                {
                    int vol = mPlayer.Volume;
                    Open(file);
                    mPlayer.Play();
                    mPlayer.Volume = vol;
                }));
                return true;
            }
        }

        void Events_TimeChanged(object sender, MediaPlayerTimeChanged e)
        {
            this.Dispatcher.BeginInvoke(new Action(delegate
            {
                label1.Content = TimeSpan.FromMilliseconds(e.NewTime).ToString().Substring(0, 8);
            }));
        }

        void Events_DurationChanged(object sender, MediaDurationChange e)
        {
            this.Dispatcher.BeginInvoke(new Action(delegate
            {
                label3.Content = TimeSpan.FromMilliseconds(e.NewDuration).ToString().Substring(0, 8);
            }));
        }
        #endregion

        private void positionComboBox_SelectionChanged(object sender, System.Windows.Controls.SelectionChangedEventArgs e)
        {
            Screen screen = Screen.FromPoint(new System.Drawing.Point((int)this.Left, (int)this.Top));
            this.Width = screen.WorkingArea.Width / 2;
            this.Height = screen.WorkingArea.Height / 2;
            object selItem = positionComboBox.SelectedItem;

            switch (selItem.ToString())
            {
                case "TL":
                    {
                        this.Left = screen.WorkingArea.Left;
                        this.Top = screen.WorkingArea.Top;
                    }
                    break;
                case "TR":
                    {
                        this.Top = screen.WorkingArea.Top;
                        this.Left = screen.WorkingArea.Right - this.Width;
                    }
                    break;
                case "BL":
                    {
                        this.Left = screen.WorkingArea.Left;
                        this.Top = screen.WorkingArea.Bottom - this.Height;
                    }
                    break;
                case "BR":
                    {
                        this.Left = screen.WorkingArea.Right - this.Width;
                        this.Top = screen.WorkingArea.Bottom - this.Height;
                    }
                    break;
            }
        }

        private void Window_SizeChanged(object sender, SizeChangedEventArgs e)
        {
        }

        private void Window_StateChanged(object sender, EventArgs e)
        {
            if (WindowState == System.Windows.WindowState.Maximized)
                mPlayerController.SendMaximized(this);
            else if (WindowState == System.Windows.WindowState.Normal)
                mPlayerController.SendDemaximized(this);
        }

        private void Window_Activated(object sender, EventArgs e)
        {
            if (mStatus != Status.Playing)
                return;
            if (syncCheckBox.IsChecked == true)
                mPlayerController.SendActivated(this);
        }
    }
}
